# coding: utf-8


from pyglet.gl import *
from pyglet.window import Window
from pyglet.sprite import Sprite
from pyglet.image import load
from pyglet.window import key
from pyglet.text import Label
from pyglet.clock import schedule_interval
# 加载avbin
from pyglet.lib import load_library
load_library('dll/avbin')
pyglet.have_avbin = True

import random

import CONTANTS


class GameView(Window):

    def __init__(self):

        # pyglet的批量绘制可以让你只通过一次简单的函数调用就将所有的东西绘制出来。
        # 你所需要做的就是创建一个batch，将它传递给每一个你要绘制的物体， 然后调用batch的draw方法
        # 窗体大小和标题
        super(GameView, self).__init__(*CONTANTS.win_size, caption='Main window')
        # 是否可以下降
        self.is_drop = True
        # 背景
        self.bg = pyglet.image.load(CONTANTS.bgImg)
        # 地图
        self.map_area = Sprite(img=load(CONTANTS.map_bgImg),
                               x=CONTANTS.map_x,
                               y=CONTANTS.map_y)
        # 分数
        self.score_int = 0
        self.score = Label('分数: %s' % str(self.score_int), x=CONTANTS.score_x, y=CONTANTS.score_y,
                           color=CONTANTS.score_color)
        # 初始化当前方块
        self.init_current_block(shape=CONTANTS.shapes.get(random.choice(CONTANTS.block_index)))
        # 初始化下一个前方块
        self.init_next_block()
        # 地图二维数据，如无方块则为0，有为1
        self.init_map_data()
        # 初始化显示地图区域内全部方块的Layer
        self.block_layer_in_map = None
        # 地图中已有方块的坐标集合
        self.map_has_block = set()

        # 背景音乐开关
        self.allow_bg_music = True
        # 播放背景音乐
        self.play_bg_music(self.allow_bg_music)

        # 播放背景音乐
        self.allow_bg_music = True
        self.play_bg_music(self.allow_bg_music)


    # 初始化当前方块
    def init_current_block(self, shape):
        # 当前方块初始位置
        pos = (CONTANTS.act_x, CONTANTS.act_y)
        # 创建当前方块
        self.current_block = Block(img='data/block_%s.png' % random.choice(CONTANTS.colors),
                                   shape=shape,
                                   pos=pos
                                   )

    # 初始化下一个方块
    def init_next_block(self):
        # 下一个方块形状
        self.next_block_shape = CONTANTS.shapes.get(random.choice(CONTANTS.block_index))
        # 下一个方块位置
        self.next_block_pos = (CONTANTS.next_block_x, CONTANTS.next_block_y)
        # 创建下一个方块
        self.next_block = Next_block(img='data/block_%s.png' % random.choice(CONTANTS.colors),
                                     shape=self.next_block_shape,
                                     pos=self.next_block_pos
                                     )

    # 地图二维数据，0表示无方块
    def init_map_data(self):
        # 地图字典坐标
        self.map_data = {}
        for i in range(CONTANTS.rows): # 行   i = 0~19
            for j in range(CONTANTS.columns):  # 列 j = 0~9
                x = (CONTANTS.columns - 1 - j) * CONTANTS.act_size + self.map_area.x + 1
                y = (CONTANTS.rows - 1 - i) * CONTANTS.act_size + self.map_area.y + 1
                self.map_data[(x,y)] = 0
        # 字典map_key,即地图坐标列表
        self.map_key = self.map_data.keys()

    # 窗口更新
    # 游戏刷新
    # dt: 更新频率
    def update(self, dt):
        # 当前方块刷新(位置)
        self.block_drop()

        pass

    # 方块平移
    # ds:+1表示向右
    # ds:-1表示向左
    def block_slip(self, ds):
        # 方块所在的地图坐标值
        self.current_block.block_area_map = {}
        # 精灵新的坐标列表
        new_position  = []
        # 每一个精灵x坐标都加上偏移量:方块大小
        for s in self.current_block.sprites:
            x = s.x + ds * CONTANTS.act_size
            # 将精灵新的坐标存放到列表
            new_position.append((x, s.y))
        # 判断是否出界, 是否能平移,是否可以下降
        if self.check_bound(new_position) and self.check_around(new_position) and self.check_drop(new_position): #
            # 将新坐标更新得方块所有精灵
            for i in range(len(new_position)):
                self.current_block.sprites[i].position = new_position[i]

    # 方块旋转
    def block_rotate(self):
        new_position = []
        # 对全部精灵进行操作
        for sprite in self.current_block.sprites:
            # 得到当前位置坐标
            x0 = sprite.x
            y0 = sprite.y
            # print("当前坐标：(%d, %d)" %(x0,y0))
            # print('-----------')
            # 顺时针90度绕自身第三个精灵旋转，坐标为
            x_ = self.current_block.sprites[2].x
            y_ = self.current_block.sprites[2].y
            # 旋转之后坐标
            x1 = y0 - y_ + x_
            y1 = -(x0 - x_) + y_
            # 将全部新坐标加入
            new_position.append((x1, y1))
        # 对全部新坐标进行判断,只要当全部新坐标都在地图区域内, 是否可以旋转,是否可以下降 才能执行旋转
        if self.check_bound(new_position) and self.check_around(new_position) and self.check_drop(new_position):
            # 将新坐标更新得方块所有精灵
            for i in range(len(new_position)):
                self.current_block.sprites[i].position = new_position[i]

    # TODO 方块秒下到底
    def block_toBottom(self):

        print('block_toBottom()')

    # 方块下降
    def block_drop(self):
        # 方块下降的速度
        self.block_drop_speed = CONTANTS.block_speed
        # 新坐标列表
        new_position = []
        # 每一个精灵y坐标都加上速度偏移
        for s in self.current_block.sprites:
            _x = s.x
            _y = s.y + self.block_drop_speed
            new_position.append((_x, _y))
        # 方块能下降,并且方块在地图区域内
        if self.check_drop(new_position) and self.check_bound(new_position) and self.check_around(new_position):
            # 将方块向下平移到新坐标
            for i in range(len(self.current_block.sprites)):
                self.current_block.sprites[i].position = new_position[i]
        else:
            # 方块不能再下降 或 方块超出底边界
            # 更新地图二维数组数据
            self.update_map(new_position)
            # 处理对象
            self.treat_block()

    # 地图上显示已前出现过的方块
    def show_block_in_map(self):
        # 读取地图二维数组中的数据
        for key in self.map_key:
            # 坐标对应的图像对象
            img = self.map_data.get(key)
            # 0 或者　图像对象
            if img:
                #　在View对象的on_draw()事件中绘制图像
                img.blit(*key)
                self.map_has_block.add(key)

    # 边界检测
    # 方块是否超出地图区域
    # block_position:新产生的位置坐标
    # 返回:
    # True: 在地图区域内
    # False: 有部分或者全部在区域外
    def check_bound(self, block_position):
        bound_list = []
        for p in block_position:
            _x = p[0]
            _y = p[1] - CONTANTS.act_size # 将当前方块的下一行与地图坐标进行对比
            is_bound = (_x, _y) in self.map_key # True表示在地图区域内
            bound_list.append(is_bound)
        # 全为True说明在区域内，只要有一个False说明超出区域
        if False in bound_list:
            return False # 超出区域
        else:
            return True

    # 方块能否再下降检测
    # block_position:新产生的位置坐标
    # 返回:
    # True: 可以下降
    # False: 不能再下降
    def check_drop(self, block_position):
        # 思路：
        # 方块坐标y轴向下偏移一个方块尺寸，得到新的坐标
        # 如果坐标被自身坐标包含，则排除这个点
        # 判断剩下的全部坐标在地图上对应的位置是不是非空
        new_p = []
        for p in block_position:
            _x = p[0]
            _y = p[1] - CONTANTS.act_size
            _p = (_x, _y)
            # 判断_p是否在block_position之中
            # 不包含则增加到新坐标中
            # 并且排除_p在地图区域之外的情况:当_y小于地图区域最下边缘时则出界了
            if (_p not in block_position) and (_y >= CONTANTS.map_y):
                new_p.append((_x, _y))
        # 判new_p坐标在地图上对应的位置是不是非空
        # 存在False or True
        is_can_drop = []
        for p1 in new_p:
            # 其值为0则为空,可以下降
            is_none = (self.map_data.get(p1) == 0)
            is_can_drop.append(is_none)
        # 如果is_can_drop存在一个False，说明方块下方相邻位置存在其他方块，则当前方块不能再下降
        if False in is_can_drop:
            return False
        else: # 如果不存在False(即is_can_drop中数据全部为真)，方块下方相邻位置不存在其他方块，则当前方块还能再下降
            return True

    # 方块变形或平移
    # 检查新坐标是否存在图形对象
    # True: 表示动作可以发生
    # False: 表示动作不可发生
    def check_around(self, block_position):
        is_around = []
        for p2 in block_position:
            # 如果p2对应的地图坐标其值为0,则说明该坐标为'空',可以存放方块:True
            is_around.append(self.map_data.get(p2) == 0)
        # 如果有一个False存在,说明不能存放方块,即不能变形或或者平稳
        if False in is_around:
            return False
        else:
            return True

    # 检测是否可以发生消除:当地图一行全占满方块的时,即地图二维数组中一行全为非0
    # 返回可以发生消除的行号列表
    def check_eliminate(self):
        # 占满的行列表
        row_full_list = []
        # 遍历地图
        # 遍历方向:从左到右/从下到上
        for i in range(CONTANTS.rows): # 行   i = 0~19
            num = 0 # 第多少行占满计数
            for j in range(CONTANTS.columns):  # 列 j = 0~9
                # 得到地图坐标
                _x = j*CONTANTS.act_size + self.map_area.x +1
                _y = i*CONTANTS.act_size + self.map_area.y +1
                # 检测地图该坐标上是否非0
                if self.map_data.get((_x, _y)):
                    num += 1 # 计数
            if num == CONTANTS.columns:
                # 将能消除的行加入到列表
                row_full_list.append(i*CONTANTS.act_size + self.map_area.y +1)
        # 当列表中有数据时,返回列表
        return row_full_list

    # 计分系统
    # row_num: 一次性消除的行数
    def add_score(self, row_num):
        # 得到当前已得分
        score = self.score_int
        if row_num == 1:
            score += 100
            self.score_int = score
        if row_num == 2:
            score += 300
            self.score_int = score
        if row_num == 3:
            score += 500
            self.score_int = score
        if row_num >= 4:
            score += 800
            self.score_int = score
        # 更新计分板
        self.score.text = "分数: %d" %score

    # 更新地图二维数组
    def update_map(self, new_position):
        # 当前方块图像
        current_img = self.current_block.img
        for p in new_position:
            # 更新地图二维数组中的数据：当前方块对应的位置，其值设为图像对象
            self.map_data[p] = current_img

    # 对象处理
    def treat_block(self):
        # 当前方块自我消灭
        self.current_block.delete()
        # 以下一个方块的形状，创建新的当前方块
        self.init_current_block(shape=self.next_block_shape)
        # 下一个方块自我消灭
        self.next_block.delete()
        # 满足条件的方块对象消除
        self.blocks_eliminate()
        # 重新生成下一个方块
        self.init_next_block()

    # 满足条件的方块对象消除
    def blocks_eliminate(self):
        # 得到能消除的行列表
        row_list = self.check_eliminate()
        # 当row_list非0时
        if len(row_list):
            # 定位到要发生消除的行
            for row in row_list:
                # 将该行所在地图二维坐标重置为0:即消除
                for j in range(CONTANTS.columns):
                    _x = j*CONTANTS.act_size + self.map_area.x +1
                    # 得到地图坐标
                    p = (_x, row)
                    # 更新地图二维数据
                    # 即完成消除操作
                    self.map_data[p] = 0
            # 方块向下融合
            self.merge_blocks(row_list)
            # 加分
            self.add_score(len(row_list))
            # 播放消除音乐
            self.play_elim_effect_music()

    # 方块融合
    # row_list: 发生整合的所在行的一个列表
    def merge_blocks(self, row_list):
        # 从最大行号（从上往下）开始
        for row in row_list[::-1]:   # [10, 39, 97]
            # 从地图左上角向右下角遍历，遍历到发生消除行的上一行为止, 比如：row = 10,即消除了最一边一行
            # 从第_i行往上遍历到最高点
            _i = (row - 1 - CONTANTS.map_y)//CONTANTS.act_size + 1
            for i in range(_i, CONTANTS.rows): #
                for j in range(CONTANTS.columns):  # 列号
                    y = CONTANTS.map_y + 1 + (i * CONTANTS.act_size)
                    x = CONTANTS.map_x + 1 + (j * CONTANTS.act_size)
                    # 得到方块
                    block = self.map_data.get((x,y))
                    if block: # 优化，只将非0坐标进行向下平移
                        # 将(x,y)0值化
                        self.map_data[(x, y)] = 0
                        # 将(x,y-方块尺寸)设为block,完成向下整合
                        self.map_data[(x, (y - CONTANTS.act_size))] = block

                    pass


    # 音效
    # 背景音乐
    # allow: 是否允许,默认为True
    def play_bg_music(self, allow=True):
        if allow:
            # 加载背景音乐
            from pyglet.lib import load_library
            # 加载AVbin
            avbin = load_library('dll/avbin32.dll')
            # 初始化avbin
            avbin.avbin_have_feature()
            # 加载音乐文件





    # 背景音乐
    def play_bg_music(self, allow=True):
        self.bg_music = pyglet.resource.media(CONTANTS.bg_music)
        if allow:
            self.bg_music.play()

    # 消除音效
    def play_elim_effect_music(self):
        self.elim_effect_music = pyglet.resource.media(CONTANTS.eliminate_music)
        self.elim_effect_music.play()







    # 窗口事件
    # 程序pyglet.app.run()会调用这个函数
    def on_draw(self):
        self.clear()
        # 绘制背景
        self.bg.blit(0, 0)
        # 绘制地图区域
        self.map_area.draw()
        # 绘制下一个方块
        self.next_block.draw()
        # 绘制当前方块
        self.current_block.draw()
        # 绘制分数显示
        self.score.draw()
        # 绘制地图区域内已存在的方块
        self.show_block_in_map()

    # 窗体监听事件
    def on_key_press(self, symbol, modifiers):
        # 暂停与开始
        if symbol == key.SPACE:
            self.is_drop = not self.is_drop


        #方块旋转
        if symbol == key.I or symbol == key.UP:
            # 判断是否进行旋转，如果为‘田’形，就不转动
            is_not_W = self.current_block.shape != CONTANTS.shapes.get('O')
            if is_not_W:
                self.block_rotate()

        # 向左平移
        if symbol == key.J or symbol == key.LEFT:
            self.block_slip(-1)
        # 向右平移
        if symbol == key.L or symbol == key.RIGHT:
            self.block_slip(1)
        # 方块秒速下移
        if symbol == key.K or symbol == key.DOWN:
            print('暂时未开发')




# 方块类
# img: 方块图像文件
# shape: 形状
class Block(Sprite):

    def __init__(self, img, shape, pos):
        # 定义方块背景图片
        self.img = pyglet.resource.image(img)
        super(Block, self).__init__(self.img, *pos)
        #根据形状创建精灵列表,组合成方块
        self.sprites = []
        # 形状数组
        self.shape = shape
        for i in range(4):
            for j in range(4):
                if self.shape[i][j]:
                    # 创建
                    x = j * 29
                    y = (3 - i) * 29
                    sprite = Sprite(self.img,x + pos[0], y + pos[1])
                    self.sprites.append(sprite)

    # 绘制
    def draw(self):
        for sprite in self.sprites:
            sprite.draw()

    # 方块坐标
    def get_block_position(self):
        p = []
        for s in self.sprites:
            p.append(s.position)
        return p


# 下一个方块
class Next_block(Block):
    def __init__(self, img, shape, pos):

        super(Next_block, self).__init__(img=img, shape=shape, pos=pos)


if __name__ == '__main__':

    g = GameView()
    schedule_interval(g.update, 1/2)
    pyglet.app.run()






